using System;
using System.Collections.Generic;

namespace System.IO
{
	/// <summary>
	/// This class provides functionality for wrapping a (network) stream up in input/output processors. The methods
	/// also use non-demanding calls to the underlying stream. That is, if data is not available, then negative
	/// or null data is returned when requested.
	/// </summary>
	public sealed class ParsedStream : System.IO.Stream, IDisposable
	{
		private const string ps_noseek = "This stream does not support Seek operations.";
		private const string us_noread = "The underlying stream does not currently support Read operations.";
		private const string us_nowrite = "The underlying stream does not currently support Write operations.";

		#region Private Variables
		/// <summary>
		/// The underlying stream that this ParsedStream is parsing the I/O for
		/// </summary>
		private System.IO.Stream							_BaseStream;

		/// <summary>
		/// The parsers that handle output to the underlying stream
		/// </summary>
		internal List<IDataProcessor>				        _OutputParsers;

		/// <summary>
		/// The parsers that handle input from the underlying stream
		/// </summary>
		internal List<IDataProcessor>				        _InputParsers;

		/// <summary>
		/// The mechanism for storing input that has been already parsed
		/// </summary>
		private System.Collections.IQueue<byte>	_ParsedInputQueue;

		/// <summary>
		/// The mechanism for storing input that hasn't been parsed yet
		/// </summary>
		private System.Collections.IQueue<byte>	_UnparsedInputQueue;

		/// <summary>
		/// The mechanism for storing output data that has been already parsed
		/// </summary>
		private System.Collections.IQueue<byte>	_ParsedOutputQueue;

		/// <summary>
		/// The mechanism for storing output data that hasn't been parsed yet
		/// </summary>
		private System.Collections.IQueue<byte>	_UnparsedOutputQueue;

		/// <summary>
		/// Indicates whether or not the underlyng stream is a NetworkStream.
		/// </summary>
		private bool _IsNetWrapper;

		private bool _AutoFlush;
		
		private bool _AutoRead;
		#endregion

		#region Constructors
		/// <summary>
		/// Default constructor. Intializes a ParsedStream with a Null underlying stream and no I/O parsers.
		/// </summary>
		private ParsedStream()
			:this(System.IO.Stream.Null)
		{
		}

		/// <summary>
		/// Initializes ParsedStream with the given underlying stream and no I/O parsers.
		/// </summary>
		/// <param name="baseStream">The underlying stream for this ParsedStream</param>
		private ParsedStream(System.IO.Stream baseStream)
			:this(baseStream,new IDataProcessor[] {},new IDataProcessor[] {})
		{
		}


		/// <summary>
		/// Initializes ParsedStream with the given underlying stream and I/O parsers, with AutoFlush and AutoRead enabled.
		/// </summary>
		/// <param name="baseStream">The underlying stream for this ParsedStream</param>
		/// <param name="inputParsers">The list of parsers to parse input from the underlying stream</param>
		/// <param name="outputParsers">The list of parsers to parse output to the underlying stream</param>
		public ParsedStream(System.IO.Stream baseStream, IDataProcessor[] inputParsers, IDataProcessor[] outputParsers)
			:this(baseStream,inputParsers,outputParsers,true,true)
		{
		}

		/// <summary>
		/// Initializes ParsedStream with the given underlying stream and I/O parsers.
		/// </summary>
		/// <param name="baseStream">The underlying stream for this ParsedStream</param>
		/// <param name="inputParsers">The list of parsers to parse input from the underlying stream</param>
		/// <param name="outputParsers">The list of parsers to parse output to the underlying stream</param>
		/// <param name="autoFlush">Specifies whether or not the outgoing data should be flushed to the underlying stream after each write action</param>
		/// <param name="autoRead">Specifies whether or not incoming data should be read and parsed automatically before reading input data</param>
		public ParsedStream(System.IO.Stream baseStream, IDataProcessor[] inputParsers, IDataProcessor[] outputParsers, bool autoFlush, bool autoRead)
		{
			_BaseStream = baseStream;
			if(_BaseStream is System.Net.Sockets.NetworkStream)
			{
				this._IsNetWrapper = true;
			}
			
			_AutoFlush = autoFlush;
			_AutoRead = autoRead;
			
			_ParsedInputQueue = new System.Collections.GenericQueue<byte>();
            _UnparsedInputQueue = new System.Collections.GenericQueue<byte>();
            _ParsedOutputQueue = new System.Collections.GenericQueue<byte>();
            _UnparsedOutputQueue = new System.Collections.GenericQueue<byte>();

            _InputParsers = new List<IDataProcessor>(inputParsers);
            _OutputParsers = new List<IDataProcessor>(outputParsers);
			
			ChainDataProcessors(_UnparsedInputQueue, inputParsers, _ParsedInputQueue);
			ChainDataProcessors(_UnparsedOutputQueue, outputParsers, _ParsedOutputQueue);
		}

		private static void ChainDataProcessors(System.Collections.IQueue<byte> inputQueue, IDataProcessor[] dataProcessors, System.Collections.IQueue<byte> outputQueue)
		{
            if (dataProcessors == null)
                return;
			if(dataProcessors.Length==0)
				return;
			if(dataProcessors.Length==1)
			{
				dataProcessors[0].Initialize(inputQueue,outputQueue);
				return;
			}
			if(dataProcessors.Length==2)
			{
				System.Collections.IQueue<byte> middleQueue = new System.Collections.GenericQueue<byte>();
				dataProcessors[0].Initialize(inputQueue,middleQueue);
				dataProcessors[1].Initialize(middleQueue,outputQueue);
				return;
			}
			for(int x=0;x<dataProcessors.Length;x++)
			{
				if(x==0)
				{
					System.Collections.IQueue<byte> nextQueue = new System.Collections.GenericQueue<byte>();
					dataProcessors[x].Initialize(inputQueue,nextQueue);
				}
				if(x>0 && x<(dataProcessors.Length-1))
				{
					System.Collections.IQueue<byte> nextQueue = new System.Collections.GenericQueue<byte>();
					dataProcessors[x].Initialize(dataProcessors[x-1].OutputQueue,nextQueue);
				}
				if(x==(dataProcessors.Length-1))
				{
					dataProcessors[x].Initialize(dataProcessors[x-1].OutputQueue,outputQueue);
					return;
				}
			}
		}

		#endregion

		#region Output
		/// <summary>
		/// Moves data through the chain of output parsers
		/// </summary>
		private void MilkOutputParsers()
		{
			if(this.OutputParsers.Length==0)
			{
				this._ParsedOutputQueue.Enqueue(this._UnparsedOutputQueue.Dequeue(this._UnparsedOutputQueue.Count));
				return;
			}
			foreach(IDataProcessor odp in this._OutputParsers)
			{
				odp.Pulse();
			}
		}

		/// <summary>
		/// Parses the given byte and writes any results to the underlying stream
		/// </summary>
		/// <param name="value">The byte to parse</param>
		public override void WriteByte(byte value)
		{
			if(this.CanWrite==false)
				throw new NotSupportedException(ParsedStream.us_nowrite);
			lock(this._UnparsedOutputQueue)
			{
				this._UnparsedOutputQueue.Enqueue(value);
			}
			this.MilkOutputParsers();
			this.AttendAutoFlush();
		}

		/// <summary>
		/// Parses the given bytes and writes any results to the underlying stream
		/// </summary>
		/// <param name="buffer">An array of bytes containing the data to parse</param>
		/// <param name="offset">The zero-based index of the data to parse</param>
		/// <param name="count">The length of the section of data to parse</param>
		public override void Write(byte[] buffer, int offset, int count)
		{
			if(this.CanWrite==false)
				throw new NotSupportedException(ParsedStream.us_nowrite);

			lock(this._UnparsedOutputQueue)
			{
				for(int x=offset;x<(offset+count);x++)
				{
					this._UnparsedOutputQueue.Enqueue(buffer[x]);
				}
			}
			this.MilkOutputParsers();
			this.AttendAutoFlush();
		}


		/// <summary>
		/// Gets the list of output parsers that this ParsedStream is using
		/// </summary>
		public IDataProcessor[] OutputParsers
		{
			get
			{
				return (IDataProcessor[])this._OutputParsers.ToArray();
			}
		}

		/// <summary>
		/// Get/Sets whether or not this ParsedStream will flush processed data to the underlying
		/// stream automatically or not.
		/// </summary>
		public bool AutoFlush
		{
			get
			{
				return this._AutoFlush;
			}
			set
			{
				this._AutoFlush = value;
			}
		}

		/// <summary>
		/// Flushes outgoing data if autoflush is on
		/// </summary>
		private void AttendAutoFlush()
		{
			if(AutoFlush)
				this.Flush();
		}

		#endregion

		#region Input

		/// <summary>
		/// Reads a sequence of parsed incoming data
		/// </summary>
		/// <param name="buffer">An array of bytes. When this method returns, the buffer contains the specified byte array with the values between offset and (offset + count - 1) replaced by the bytes read from the current source</param>
		/// <param name="offset">The zero-based byte offset in buffer at which to begin storing the data read from the current stream</param>
		/// <param name="count">The maximum number of bytes to be read from the current stream</param>
		/// <returns> The total number of bytes read into the buffer.  This can be less than the number of bytes requested if that many bytes are not currently available, or zero (0) if the end of the stream has been reached</returns>
		public override int Read(byte[] buffer, int offset, int count)
		{
			if(this.CanRead==false)
				throw new NotSupportedException(ParsedStream.us_noread);
            if (buffer == null)
                throw new ArgumentNullException("buffer");
            if (buffer.Length < (offset + count))
                throw new IndexOutOfRangeException();
			
			this.AttendAutoRead();
			this.MilkInputParsers();
			
			if(this._ParsedInputQueue.Count==0)
				return 0;
            
			int x;
			lock(this._ParsedInputQueue)
			{
				for(x=offset;(x<(offset+count))&&(this._ParsedInputQueue.Count>0);x++)
				{
					buffer[x] = this._ParsedInputQueue.Dequeue();
				}
			}

			return (x-offset);
		}

		/// <summary>
		/// Reads a byte of parsed incoming data
		/// </summary>
		/// <returns>Returns a byte of parsed incoming data if any exists, otherwise -1</returns>
		public override int ReadByte()
		{
			if(this.CanRead==false)
				throw new NotSupportedException(ParsedStream.us_noread);
			
			this.AttendAutoRead();
			this.MilkInputParsers();
			
			lock(this._ParsedInputQueue)
			{
				if(this._ParsedInputQueue.Count==0)
					return -1;
				else
					return this._ParsedInputQueue.Dequeue();
			}
		}

		/// <summary>
		/// Returns the first available byte that has been returned from parsing/processing operations.
		/// If no data is available, -1 is returned.
		/// </summary>
		internal int Peek()
		{
			if(this.CanRead==false)
				throw new NotSupportedException(ParsedStream.us_noread);

			this.AttendAutoRead();
			this.MilkInputParsers();
			lock(this._ParsedInputQueue)
			{
				if(this._ParsedInputQueue.Count==0)
				{
					return -1;
				}
				else
				{
					return (int)this._ParsedInputQueue.Peek();
				}
			}
		}

		private bool OkayForImmediateRead()
		{
			if(this._IsNetWrapper)
			{
				bool b;
				try
				{
					b = ((System.Net.Sockets.NetworkStream)this._BaseStream).DataAvailable;
				}
				catch(System.ObjectDisposedException e)
				{
					throw new System.IO.IOException("Cannot read from NetworkStream, it has been disposed.",e);
				}

				return b;
			}
			else
				return true;
		}

		/// <summary>
		/// Moves data through the chain of input parsers and returns any results
		/// </summary>
		private void MilkInputParsers()
		{
			if(this.InputParsers.Length==0)
			{
				this._ParsedInputQueue.Enqueue(this._UnparsedInputQueue.Dequeue(this._UnparsedInputQueue.Count));
				return;
			}
			foreach(IDataProcessor idp in this._InputParsers)
			{
				idp.Pulse();
			}	
		}

		/// <summary>
		/// Gets the list of input parsers that this ParsedStream is using
		/// </summary>
		public IDataProcessor[] InputParsers
		{
			get
			{
				return (IDataProcessor[])_InputParsers.ToArray();
			}
		}

		public bool AutoRead
		{
			get
			{
				return this._AutoRead;	
			}
			set
			{
				this._AutoRead = value;
			}
		}

		private void AttendAutoRead()
		{
			if(AutoRead)
				this.UpdateInput();
		}
		#endregion

		#region Functionality Detection

		/// <summary>
		/// Gets a boolean value indicating whether or not the underlying stream is supporting
		/// and allowing Read operations.
		/// </summary>
		public override bool CanRead
		{
			get
			{
				return _BaseStream.CanRead;
			}
		}

		/// <summary>
		/// Gets a boolean value indicating whether or not this stream supports Seek operations.
		/// Always returns False.
		/// </summary>
		public override bool CanSeek
		{
			get
			{
				return false;
			}
		}

		/// <summary>
		/// Gets a boolean value indicating whether or not the underlying stream is supporting
		/// and allowing Write operations.
		/// </summary>
		public override bool CanWrite
		{
			get
			{
				return _BaseStream.CanWrite;
			}
		}

		#endregion

		#region Seek-related Stubs
		
		/// <summary>
		/// [System.IO.Stream].Seek() - Not supported on a ParsedStream
		/// </summary>
		public override long Seek(long offset, SeekOrigin origin)
		{
			throw new NotSupportedException(ParsedStream.ps_noseek);
		}

		/// <summary>
		/// [System.IO.Stream].SetLength() - Not supported on a ParsedStream
		/// </summary>
		public override void SetLength(long value)
		{
			throw new NotSupportedException(ParsedStream.ps_noseek);
		}

		/// <summary>
		/// [System.IO.Stream].Length{get;} - Not supported on a ParsedStream
		/// </summary>
		public override long Length
		{
			get
			{
				throw new NotSupportedException(ParsedStream.ps_noseek);
			}
		}

		/// <summary>
		/// [System.IO.Stream].Position{get;set;} - Not supported on a ParsedStream
		/// </summary>
		public override long Position
		{
			get
			{
				throw new NotSupportedException(ParsedStream.ps_noseek);
			}
			set
			{
				throw new NotSupportedException(ParsedStream.ps_noseek);
			}
		}

		#endregion

		#region Misc
		/// <summary>
		/// Closes the current stream and releases any resources assoiciatted with it
		/// </summary>
		public override void Close()
		{
			//this.AttendAutoFlush();
			//this.AttendAutoRead();
			_BaseStream.Close();
		}

		/// <summary>
		/// Writes parsed output data to the underlying stream. Thread safe.
		/// </summary>
		public override void Flush()
		{
			byte[] bo;
			lock(this._ParsedOutputQueue)
			{
				bo = this._ParsedOutputQueue.Dequeue(this._ParsedOutputQueue.Count);
			}
			lock(this._BaseStream)
			{
				this._BaseStream.Write(bo,0,bo.Length);
			}
		}

		/// <summary>
		/// Collects as much input from the underlying stream as possible. Thread safe.
		/// </summary>
		public void UpdateInput()
		{
			int b;
			lock(this._BaseStream)
			{
				if(!OkayForImmediateRead())
					return;
				b = _BaseStream.ReadByte();
			}
			while(b != -1)
			{
				lock(this._UnparsedInputQueue)
				{
					this._UnparsedInputQueue.Enqueue((byte)b);
				}
				lock(this._BaseStream)
				{
					if(!OkayForImmediateRead())
						return;
					b = _BaseStream.ReadByte();
				}
			}
		}

		/// <summary>
		/// A stream with no backing store and no parsers
		/// </summary>
		public new static ParsedStream Null
		{
			get
			{
				return new ParsedStream();
			}
		}
		#endregion

        #region IDisposable Members

        void IDisposable.Dispose()
        {
            List<System.IO.IDataProcessor> l = new List<System.IO.IDataProcessor>();
            l.AddRange(_InputParsers);
            l.AddRange(_OutputParsers);
            foreach (System.IO.IDataProcessor dp in l)
            {
                lock (dp)
                {
                    dp.Dispose();
                }
            }
        }

        #endregion
    }
}